/*
 * colourlicprocessor.cpp
 *
 * Copyright 2012 The University of Auckland
 *
 *  Created on: 17/08/2012
 *      Author: cvan680
 */

#include "colourlicprocessor.h"
#include "custommodules/ccvethesis/datastructures/mftgtexture.h"
#include "modules/flowreen/datastructures/streamlinetexture.h"
#include "tgt/textureunit.h"
#include "modules/flowreen/utils/flowmath.h"
#include <vector>

#include <windows.h>
#include <math.h>
//using std::vector;
using tgt::TextureUnit;

namespace voreen {

/*
 * TODO:
 * - Do we need a RenderProcessor? Or maybe VolumeProcessor? Or VolumeRaycaster..?
 * - Multiple Volume in-ports (ideally a property to choose the number of inports so to variably create or remove them)
 */

ColourLICProcessor::ColourLICProcessor()
    : RenderProcessor()
	, inport_(Port::INPORT, "volumecollection", 0)
    , volPort1_(Port::INPORT, "volumehandle.volumehandle1")
    , volPort2_(Port::INPORT, "volumehandle.volumehandle2")
    , outport_(Port::OUTPORT, "outport")
	, grammarfile_("grammarfile", "Texture Grammar", "Select texture grammar",
                VoreenApplication::app()->getResourcePath("textures"), "Texture Grammar files (*.teg)",
                FileDialogProperty::OPEN_FILE, Processor::INVALID_RESULT)
	, kernelSizeProp_("kernelSizeProp", "max. convolution kernel size field 1: ", 7, 1, 100)
	, pixelSamplingProp_("pixelSamplingProp", "pixel sampling field 1: ", 50, 1, 10000)
	, useAdaptiveKernelSizeProp_("useAdaptiveKernelSizeProp", "adaptive kernel size field 1: ", false)
	, alignmentProp_(0)
	, comboProp_(0)
	, weightingFactor_("weightingFactor", "Weighting factor", 0.5f, 0.0f, 1.0f)
	, thresholdProp_("magnitudeThreshold", "threshold for magnitude (%): ", tgt::vec2(0.0f, 100.0f), tgt::vec2(0.0f), tgt::vec2(100.0f))
	, sliceIndexProp_("sliceIndex", "slice number: ", 1, 1, 100)
	, sliceTexture_(0)
	, colourTexture(0)
	, permutation_(0,1,2)
	, update_prop("update", "Update outputs")
	, shader_(0)
	, LICchanged(true)
{

    alignmentProp_ = new OptionProperty<SliceAlignment>("sliceAlignment", "slice alignment: ");
    alignmentProp_->addOption("xy-plane", "xy-plane", PLANE_XY);
    alignmentProp_->addOption("xz-plane", "xz-plane", PLANE_XZ);
    alignmentProp_->addOption("zy-plane", "zy-plane", PLANE_ZY);
    alignmentProp_->onChange(
        CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::onSliceAlignmentChange));


    comboProp_ = new OptionProperty<ComboType>("comboType", "Combine fields using: ");
    comboProp_->addOption("multiplication", "multiplication", MULTIPLICATION);
    comboProp_->addOption("weighted_addition", "weighted addition", WEIGHTED_ADDITION);
    comboProp_->onChange(
        CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::process));
    weightingFactor_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::process));

    thresholdProp_.setStepping(tgt::vec2(1.0f));
    thresholdProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::onThresholdChange));

	kernelSizeProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));
	pixelSamplingProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));
	useAdaptiveKernelSizeProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));
	alignmentProp_->onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));
	thresholdProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));
	sliceIndexProp_.onChange(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::toggleLIC));

    // register ports and properties
    //addPort(inport_);
    addPort(volPort1_);
    addPort(volPort2_);
    addPort(outport_);
    addProperty(grammarfile_);
    addProperty(kernelSizeProp_);
    addProperty(pixelSamplingProp_);
    addProperty(useAdaptiveKernelSizeProp_);
    addProperty(alignmentProp_);
    addProperty(thresholdProp_);
    addProperty(sliceIndexProp_);
    addProperty(comboProp_);
    addProperty(weightingFactor_);

	update_prop.onClick(CallMemberAction<ColourLICProcessor>(this, &ColourLICProcessor::process));

    addProperty(update_prop);
}

Processor* ColourLICProcessor::create() const {
    return new ColourLICProcessor();
}

void ColourLICProcessor::process() {
	tgt::vec2 viewportSize = outport_.getSize();

		// LIC only
	int SAMPLING = pixelSamplingProp_.get();;
	int KERNELSIZE = kernelSizeProp_.get();
	int SLICENO = sliceIndexProp_.get(); // FIXME get property to auto update

	bool blaat = false;
	const VolumeFlow3D* volflow1 = checkVolumeHandleForFlowData<VolumeFlow3D>(volPort1_.getData(), blaat);
	const VolumeBase* scalarVolume = volPort2_.getData();

	if(!volflow1 || !scalarVolume){ return; }

	const Flow3D& flow1 = volflow1->getFlow3D();

	tgt::vec2 texDim2D = static_cast<tgt::vec2>(flow1.getFlowSliceDimensions(permutation_));
	float textureScaling = getTextureScalingFactor(viewportSize, texDim2D);

	tgt::vec2 outputSize(texDim2D * textureScaling);  // size of the quad on which the texture will be rendered
	tgt::vec2 slicePos = (viewportSize - outputSize) / 2.0f;    // offset for the quad on which the texture will be rendered

	Flow2D flow2D = flow1.extractSlice(permutation_, SLICENO);

	const tgt::ivec2 dims(flow2D.dimensions_ * static_cast<int>(textureScaling));

	// FIXME HACK - make sliceIndexProp_ adjustable to flow volume and permutation
	SLICENO = sliceIndexProp_.get() - 1;

	const tgt::vec2& THRESHOLDS(flow1.maxMagnitude_ * tgt::vec2(0.0f, 100.0f) / 100.0f);

	SimpleTexture<float> noiseTexture(flow2D.dimensions_, true);
	noiseTexture.createWhiteNoise();

	int numElements = dims.x * dims.y;
	// FIXME LIC turned off just to speed things up

	if(LICchanged || !pixels){
		pixels = StreamlineTexture<float>::fastLIC(flow2D, noiseTexture,
					textureScaling, SAMPLING, KERNELSIZE, THRESHOLDS, false);
	}
	LICchanged = false;
/*
	tgt::Texture* LICTexture = new tgt::Texture(reinterpret_cast<GLubyte*>(pixels1),
		tgt::ivec3(dims, 1), GL_LUMINANCE, GL_LUMINANCE,
		GL_FLOAT, tgt::Texture::NEAREST);
*/
	// VOLUME STUFF
	const VolumeRAM* volram = scalarVolume->getRepresentation<VolumeRAM>();

	if(!colourTexture){
		MFTGTexture* blaataap = new MFTGTexture(dims);
		std::cout << "\nprint BUILT OBJECT";
		colourTexture =
				blaataap->scalarSliceToIntensity( volram, SLICENO, dims,
												  static_cast<int>(textureScaling),
												  alignmentProp_->getValue() );
	}


/*
	outport_.activateTarget("ColourLICProcessor::process()");
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	shader_->activate();
	setGlobalShaderParameters(shader_);
	outport_.setTextureParameters(shader_, "texParams_");

	// TODO I am here now - upload both textures to shader
	TextureUnit* texUnitCOLOUR;
	TextureUnit* texUnitLIC;
	texUnitCOLOUR->activate();
	colourTexture->bind();
	shader_->setUniform("colourTex_", texUnitCOLOUR->getUnitNumber());
	//LGL_ERROR;
	colourTexture->uploadTexture();

	texUnitLIC->activate();
	LICTexture->bind();
	shader_->setUniform("LICTex_", texUnitLIC->getUnitNumber());
	//LGL_ERROR;
	LICTexture->uploadTexture();
	// FIXME hopefully I now have both textures at my disposal...?
	renderQuad();
	shader_->deactivate();
	outport_.deactivateTarget();
	tgt::TextureUnit::setZeroUnit();



	sliceTexture_ = new tgt::Texture(reinterpret_cast<GLubyte*>(colours),
		tgt::ivec3(dims, 1), GL_RGBA, GL_RGBA,
		GL_FLOAT, tgt::Texture::NEAREST);

*/


	float* colours(new float[numElements * 4]);
	memset(colours, 0, sizeof(float) * numElements * 4);

	float weight = weightingFactor_.get();

	for(int i=0; i < numElements; ++i){
		tgt::vec4 col = getColour(colourTexture[i]);

		if(comboProp_->getValue() == MULTIPLICATION){
			colours[i*4+0] = col.r * pixels[i];
			colours[i*4+1] = col.g * pixels[i];
			colours[i*4+2] = col.b * pixels[i];
			colours[i*4+3] = col.a * pixels[i];
		}else if(comboProp_->getValue() == WEIGHTED_ADDITION){

			colours[i*4+0] = col.r * weight + pixels[i] * (1-weight);
			colours[i*4+1] = col.g * weight + pixels[i] * (1-weight);
			colours[i*4+2] = col.b * weight + pixels[i] * (1-weight);
			colours[i*4+3] = col.a * weight + pixels[i] * (1-weight);
		}

	}


	sliceTexture_ = new tgt::Texture(reinterpret_cast<GLubyte*>(colours),
		tgt::ivec3(dims, 1), GL_RGBA, GL_RGBA,
		GL_FLOAT, tgt::Texture::NEAREST);

/*	sliceTexture_ = LICTexture; */

	GLuint texID = 0;
	if (sliceTexture_ != 0) {
		sliceTexture_->uploadTexture();
		glBindTexture(GL_TEXTURE_2D, 0);
		//rebuildTexture_ = false;
		texID = sliceTexture_->getId();
	}

	std::cout << "\nprint 5";

	outport_.activateTarget("ColourLICProcessor::process()");

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewportSize.x, 0.0f, viewportSize.y, -1.0f, 1.0f);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glActiveTexture(GL_TEXTURE0);
	if (texID != 0) {
		// Bind the texture containing the flow image, if one has been generated
		//
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, texID);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	}

	glPushAttrib(GL_TEXTURE_BIT);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glTranslatef(slicePos.x, slicePos.y, 0.0f);
	glBegin(GL_QUADS);
		glMultiTexCoord2f(GL_TEXTURE0, 0.0f, 0.0f);
		glVertex2f(0.0f, 0.0f);

		glMultiTexCoord2f(GL_TEXTURE0, 1.0f, 0.0f);
		glVertex2f(outputSize.x, 0.0f);

		glMultiTexCoord2f(GL_TEXTURE0, 1.0f, 1.0f);
		glVertex2f(outputSize.x, outputSize.y);

		glMultiTexCoord2f(GL_TEXTURE0, 0.0f, 1.0f);
		glVertex2f(0.0f, outputSize.y);
	glEnd();

	glPopAttrib();
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glDisable(GL_TEXTURE_2D);

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	outport_.deactivateTarget();


/*

	// GONNA TRY TO PLAY WITH SHADER HERE TO INCORP. COLOURMAP IN SCALAR FIELD
	sliceTexture_->uploadTexture();
	glBindTexture(GL_TEXTURE_2D, 0);

		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, sliceTexture_->getId());

		shader_->activate();


*/

	// END OF PLAYING WITH SHADER
}



/**
 * FIXME not sure if I need this? Maybe later. Adjusted from flowslicerenderer2 in that I
 * don't actually use the zoomProperty thingy, I just set it to ZOOM_FULL_SIZE in initialization.
 */
float ColourLICProcessor::getTextureScalingFactor(const tgt::vec2& viewportSize,
                                             const tgt::vec2& textureSize)
{
    float scaleWidth = viewportSize.x / textureSize.x;
    float scaleHeight = viewportSize.y / textureSize.y;
    float scaling = (scaleWidth <= scaleHeight) ? scaleWidth : scaleHeight;
    if (zoom_ != ZOOM_FULL_SIZE) {
        // find minimal scaling factor
        //
        float zoom = static_cast<float>(zoom_);
        if (zoom <= scaling)
            scaling = zoom;
        else {
            zoom_ = ZOOM_FULL_SIZE;
        }
    }
    return scaling;
}


float ColourLICProcessor::calculateVectorMagnitude( tgt::vec3 v ) {
	return sqrtf(tgt::dot(v, v));
}



void ColourLICProcessor::onThresholdChange() {
    tgt::vec2 threshold(thresholdProp_.get());
    if (threshold.x > threshold.y)
        threshold.y = threshold.x;
    thresholdProp_.set(threshold);
}

void ColourLICProcessor::onSliceAlignmentChange() {
    alignment_ = alignmentProp_->getValue();
    permutation_ = getCoordinatePermutation(alignment_);
    //rebuildTexture_ = true;
    //updateNumSlices();
    //rebuildShader();
}

tgt::ivec3 ColourLICProcessor::getCoordinatePermutation(const SliceAlignment& alignment) {
    switch (alignment) {
        case PLANE_XY:
            return tgt::ivec3(0, 1, 2);
        case PLANE_XZ:
            return tgt::ivec3(0, 2, 1);
        case PLANE_ZY:
            return tgt::ivec3(2, 1, 0);
    }
    return tgt::ivec3(0, 0, 0);
}

bool ColourLICProcessor::loadShader( const std::string& vertexShaderName,
                                const std::string& fragmentShaderName )
{
    if (shader_ == 0) {
        try {
            shader_ = ShdrMgr.loadSeparate(vertexShaderName.c_str(), fragmentShaderName.c_str(),
                generateShaderHeader(), false);
        } catch(tgt::Exception) {
            cout << "Failed to load shaders '" <<  vertexShaderName << "' & '"
                << fragmentShaderName << "'!";
            return false;
        }
        if (shader_ == 0)
            return false;

        shader_->deactivate();
        //shader_->setHeaders(generateShaderHeader());
        shader_->rebuild();
    }
    return true;
}

std::string ColourLICProcessor::generateShaderHeader(){
/* FIXME write all the values to shader header here
 *
    std::ostringstream oss;
    oss << "#define COLOR_MODE " << static_cast<int>(colorCodingMode_) << "\n";
    if (colorCodingModeProp_.getValue() == COLOR_CODING_MODE_MAGNITUDE)
        oss << "#define COLOR_TABLE " << colorTableProp_.getValue() << "\n";
    return oss.str();
    */
	return "";
}

tgt::vec4 ColourLICProcessor::getColour(float value) {

	if(value > 1.0){ value = 1.0; }
	if(value < 0.0){ value = 0.0; }

	/*
	const int colorTableSize = 4;
	tgt::vec3 colorTable[colorTableSize];
	colorTable[0] = tgt::vec3(0.0, 0.0, 0.0);    // black
	colorTable[1] = tgt::vec3(1.0, 0.0, 0.0);    // red
	colorTable[2] = tgt::vec3(1.0, 1.0, 0.0);    // yellow
	colorTable[3] = tgt::vec3(1.0, 1.0, 1.0);    // white
*/
	// COOL TO WARM
	const int colorTableSize = 5;
	tgt::vec3 colorTable[colorTableSize];
	colorTable[0] = tgt::vec3(0.2313725, 0.2980392, 0.7529412);
	colorTable[1] = tgt::vec3(0.5529412, 0.6901961, 0.9960784);
	colorTable[2] = tgt::vec3(0.8666667, 0.8666667, 0.8666667);
	colorTable[3] = tgt::vec3(0.9568627, 0.6039216, 0.4823529);
	colorTable[4] = tgt::vec3(0.70588235, 0.01568627, 0.14901961);

	float numColors = float(colorTableSize - 1);
	float v = value * numColors;
	tgt::vec3 color = mix( colorTable[int(v)],
						   colorTable[int(ceil(v))],
						   (v - int(v)) );
	return tgt::vec4(color, 1.0);
}

float ColourLICProcessor::clamp(float value, float min, float max) {
    return value < min ? min : (value > max ? max : value);
}

int ColourLICProcessor::clamp2(int value, int min, int max) {
    return value < min ? min : (value > max ? max : value);
}

tgt::vec3 ColourLICProcessor::mix(tgt::vec3 vec1, tgt::vec3 vec2, float fraction){

	tgt::vec3 result( (vec1.r * (1.0-fraction) + vec2.r * fraction),
				 	  (vec1.g * (1.0-fraction) + vec2.g * fraction),
				 	  (vec1.b * (1.0-fraction) + vec2.b * fraction) );
	return result;
	//return (fraction*vec2 + (1-fraction)*vec2);
}

void ColourLICProcessor::initialize() throw (VoreenException) {
    // call superclass function first
    RenderProcessor::initialize();

    zoom_ = ZOOM_FULL_SIZE;

    // loadSeparate with including a vertex shader?
    shader_ = ShdrMgr.loadSeparate("passthrough.vert", "colorlic.frag", RenderProcessor::generateHeader(), false);
}

void ColourLICProcessor::deinitialize() throw (VoreenException) {
    // free shader
    ShdrMgr.dispose(shader_);
    shader_ = 0;

    // call superclass function last
    RenderProcessor::deinitialize();
}

void ColourLICProcessor::toggleLIC(){
	LICchanged = true;
}


} // namespace
